package com.fengwk.resource.service.impl;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import javax.annotation.PostConstruct;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;

import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.InvalidRemoteException;
import org.eclipse.jgit.api.errors.TransportException;
import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.URIish;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import com.fengwk.resource.dao.ResServerDao;
import com.fengwk.resource.face.constant.ResourceConstant;
import com.fengwk.resource.face.entity.ResFile;
import com.fengwk.resource.face.entity.ResServer;
import com.fengwk.resource.face.model.File;
import com.fengwk.resource.face.model.GitServer;
import com.fengwk.resource.service.FileService;
import com.fengwk.resource.service.UploadService;
import com.fengwk.support.cache.Cache;
import com.fengwk.support.code.Sequence;
import com.fengwk.support.exception.BaseException;
import com.fengwk.support.util.ArrayUtils;
import com.fengwk.support.util.BeanUtils;
import com.fengwk.support.util.CacheUtils;
import com.fengwk.support.util.CodeUtils;
import com.fengwk.support.util.CollectionUtils;
import com.fengwk.support.util.DateUtils;
import com.fengwk.support.util.FileExtensionUtils;
import com.fengwk.support.util.FileUtils;
import com.fengwk.support.util.HttpTestUtils;
import com.fengwk.support.util.JGitUtils;
import com.fengwk.support.util.ObjectUtils;
import com.fengwk.support.util.StringUtils;
import com.fengwk.support.util.ThumbnailatorUtils;
import com.fengwk.support.util.WebUtils;

/**
 * 使用git作为文件存储服务器
 * 
 * @author fengwk
 *
 */
@Service
public class GitUploadServiceImpl implements UploadService {
	
	private static final String IMG_SIZE_SPLIT = "*";
	
	private static final String BLANK = "";
	
	private static final String UNDERLINE = "_";
	
	private volatile static List<GitServer> GITS;
	
	private volatile static int CURRENT_GIT;
	
	private static final long ALL_SERVER_CACHE_TIMEOUT = 1000 * 30;
	
	private static volatile Cache<List<ResServer>> ALL_SERVER_CACHE;
	
	private static final long CHECK_200_SLEEP_TIME = 300;
	
	private static final int CHECK_200_MAX_COUNT = 200;
	
	@Value("${constant.resource.pre-file-absolute-path}")
	private String preFileAbsolutePath;
	
	@Autowired
	private FileService fileService;
	
	@Autowired
	private ResServerDao rsDao;
	
	// 必须先初始化,否则会删除文件
	@PostConstruct
	private synchronized void init() throws InvalidRemoteException, TransportException, GitAPIException, IllegalStateException, IOException {
		if (GITS != null)
			return;
		
		// 初始化gits
		GITS = new Vector<>();
		// 查询可用的服务器
		List<ResServer> rsList = rsDao.queryByTypeAndStatus(ResServer.TYPE_GIT, ResServer.STATUS_ACCEPT);
		if (CollectionUtils.isBlank(rsList)) 
			throw new BaseException("没有可用的git资源服务器");

		for (ResServer rs: rsList) {
			String name = rs.getName();
			String remote = rs.getUrl();
			String username = rs.getUsername();
			String password = rs.getPassword();
			// 本地仓库为preFileAbsolutePath+服务器名称
			java.io.File root = FileUtils.buildFile(gitLocal(name), true);
			// 扫描git目录
			java.io.File gitDir = JGitUtils.scanGitDir(root);
			// git
			Git git = null;
			if (gitDir != null)
				git = JGitUtils.open(root, false);
			
			if (git != null) {
				boolean match = false;
				// 匹配远程仓库地址是否符合
				List<RemoteConfig> rcList = git.remoteList().call();
				for (RemoteConfig rc: rcList) {
					List<URIish> uriList = rc.getURIs();
					// 地址匹配
					for(URIish uri: uriList) {
						if (ObjectUtils.equals(uri.toString(), remote)) {
							match = true;
							break;
						}
					}
				}
				if (!match) {
					// 不匹配,需要重新拉取远程仓库
					git.close();
					git = null;
				}
			}
			
			// 重新克隆库,可能用时较长
			if (git == null) {
				FileUtils.delete(root, false);
				git = JGitUtils.clone(remote, root, username, password);
			}
			// 转换
			GitServer gs = BeanUtils.convert(rs, GitServer.class);
			gs.setGit(git);
			GITS.add(gs);
		}
		CURRENT_GIT = 0;
	}
	
	private String gitLocal(String name) {
		return preFileAbsolutePath + WebUtils.SEPARATOR + name;
	}
	
	// 获取Git
	private static synchronized GitServer gitServer() {
		int i = CURRENT_GIT ++;
		if (CURRENT_GIT >= GITS.size())
			CURRENT_GIT = 0;
		return GITS.get(i);
	}
	
	@Transactional
	@Override
	public List<File> files(Long userId, HttpServletRequest request) throws IOException, InvalidRemoteException, TransportException, GitAPIException, InterruptedException {
		GitServer gs = gitServer();
		String local = gitLocal(gs.getName());
		Long serverId = gs.getServerId();
		
		List<ResFile> rfList = new ArrayList<>();
		List<ResFile> serverSaveRfList = new ArrayList<>();
		MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request;
		// 获取multiRequest中所有的文件名
		Iterator<String> it = multiRequest.getFileNames();
		while (it.hasNext()) {
			MultipartFile multipartFile = multiRequest.getFile(it.next().toString());
			if (multipartFile != null) {
				// 文件序号
				Long fileId = Sequence.next();
				
				// 文件名称和类型
				String name = multipartFile.getOriginalFilename();
				String type = null;
				
				// 获取文件后缀
				String[] typeSplit = FileExtensionUtils.split(name);
				if(ArrayUtils.isNotBlank(typeSplit)) {
					name = typeSplit[0];
					type = typeSplit[1];
				}
				
				// 获取文件尺寸,占用大小
				String size = null;
				Long length = null;
				
				// 文件相对路径和绝对路径
				String relativePath = null;
				String absolutePath = null;
				
				// 文件md5摘要
				String md5 = CodeUtils.md5Hex(multipartFile.getBytes());
				File sameMd5File = fileService.getLastByMd5(md5);
				if (sameMd5File == null || !HttpTestUtils.is200(sameMd5File.getRealUrl())) {
					// 之前不存在的文件
					// 绝对路径
					String dateFolder = WebUtils.SEPARATOR + DateUtils.current(DateUtils.YYYYMMDD);
					String preAbsolute = local + dateFolder;
					
					// 最后一级
					String fName= fileId.toString();
					if (name != null)
						fName = name + UNDERLINE + fName;
					String saveUri = WebUtils.SEPARATOR + fName;
					String suffix = BLANK;
					if (StringUtils.isNotBlank(type))
						suffix = FileExtensionUtils.EXTENTSION_CHARACTER + type;
					
					absolutePath = preAbsolute + saveUri + suffix;
					relativePath = dateFolder + saveUri + suffix;
					
					java.io.File file = FileUtils.buildFile(absolutePath, true);
					multipartFile.transferTo(file);
					
					if(!file.exists())
						throw new BaseException("上传文件为空或解析拷贝异常");
						
					length = file.length();
					
					// 若文件为图片则生成缩略图
					if(type != null && fileService.shouldThumbnail(type)) {
						// 添加缩略图后缀
						String saveThumbnail = preAbsolute + saveUri + ResourceConstant.THUMBNAIL_SUFFIX + suffix;
						ThumbnailatorUtils.thumbnail(absolutePath, saveThumbnail, ResourceConstant.THUMBNAIL_QUALITY);
						BufferedImage bi = ImageIO.read(new java.io.File(absolutePath));
						size = bi.getWidth() + IMG_SIZE_SPLIT + bi.getHeight();
					}
				} else {
					// 已存在文件,重新存储一个副本
					size = sameMd5File.getSize();
					length = sameMd5File.getLength();
					relativePath = sameMd5File.getRelativePath();
					serverId = sameMd5File.getServerId();
				}
				// git服务器存储没有absolutePath
				ResFile rf = fileService.save(fileId, name, type, size, length, relativePath, null, userId, md5, serverId, null);
				// 加入临时的绝对路径供saveToServer方法使用
				rf.setAbsolutePath(absolutePath);
				rfList.add(rf);
				
				if (sameMd5File == null)
					serverSaveRfList.add(rf);
			}
		}
		
		// 由文件服务器转存
		if (CollectionUtils.isNotBlank(serverSaveRfList))
			saveToServer(serverSaveRfList, gs, local);
		
		List<File> fileList = fileService.toFileList(rfList);
		
		// 阻塞并核查状态
		check200(fileList);
		
		return fileList;
	}
	
	private void saveToServer(Collection<ResFile> rfs, GitServer gs, String local) throws InvalidRemoteException, TransportException, GitAPIException, InterruptedException {
		List<String> filePaths = new ArrayList<>();
		List<String> relativePaths = new ArrayList<>();
		for (ResFile rf: rfs) {
			String absolutePath = rf.getAbsolutePath();
			filePaths.add(absolutePath);
			relativePaths.add(StringUtils.trimPrefix(absolutePath, local + WebUtils.SEPARATOR));
			
			// 是否生成缩略图
			String type = rf.getType();
			if(type != null && fileService.shouldThumbnail(type)) {
				int s = absolutePath.lastIndexOf(type) - 1;
				String thumbnail = absolutePath.substring(0, s) 
						+ ResourceConstant.THUMBNAIL_SUFFIX 
						+ absolutePath.substring(s);
				filePaths.add(thumbnail);
				relativePaths.add(StringUtils.trimPrefix(thumbnail, local + WebUtils.SEPARATOR));	
			}
			
			// 重新置空临时路径
			rf.setAbsolutePath(null);
		}
		
		gs.save(relativePaths);
		
		// 删除原文件
		for (String path: filePaths)
			FileUtils.delete(path);
		
		
	}
	
	// 检测git服务器存储状态,并阻塞
	private void check200(Collection<File> fs) throws IOException, InterruptedException {
		if (CollectionUtils.isBlank(fs))
			return;
		
		int count = 0;
		boolean is200 = false;
		do {
			if (count ++ >= CHECK_200_MAX_COUNT)
				throw new BaseException("check200 fail: count ++ >= CHECK_200_MAX_COUNT");
			
			Thread.sleep(CHECK_200_SLEEP_TIME);
			int c = 0;
			for (File f: fs) {
				if (!HttpTestUtils.is200(f.getRealUrl()))
					break;
				c ++;
			}
			is200 = c == fs.size();
		} while(!is200);
	}
	
	@Override
	public List<File> urls(Long userId, Collection<String> urls) throws MalformedURLException {
		List<ResServer> rsList = null;
		if (ALL_SERVER_CACHE == null || ALL_SERVER_CACHE.isTimeout()) {
			rsList = rsDao.queryAll();
			ALL_SERVER_CACHE = CacheUtils.cache(rsList, ALL_SERVER_CACHE_TIMEOUT);
		} else 
			rsList = ALL_SERVER_CACHE.get();
		
		List<ResFile> rfList = new ArrayList<>();
		if (CollectionUtils.isNotBlank(rsList)) {
			for (String url: urls) {
				Long fileId = Sequence.next();
				// 获取名称和类型
				URL u = new URL(url);
				String path = u.getPath();
				int i = path.lastIndexOf(WebUtils.SEPARATOR);
				String name = StringUtils.substring(url, i + 1);
				String type = null;
				String[] splits = FileExtensionUtils.split(name);
				if (ArrayUtils.isNotBlank(splits)) {
					name = splits[0];
					type = splits[1];
				}
				
				String relativePath = null;
				Long serverId = null;
				String linkUrl = null;
				// 尝试关联到服务器
				for (ResServer rs: rsList) {
					String pre = rs.getFileUrlPrefix();
					if (url.startsWith(pre)) {
						// 关联到服务器
						relativePath = StringUtils.substring(url, pre.length());
						serverId = rs.getServerId();
						break;
					}
				}
				
				// 不能关联到服务器则使用linkUrl
				if (relativePath == null || serverId == null)
					linkUrl = url;
				
				ResFile rf = fileService.save(
						fileId, name, type, null, null, null, null, 
						userId, null, serverId, linkUrl);
				rfList.add(rf);
			}
		}
		return fileService.toFileList(rfList);
	}

}
